package com.sanq.product.websocket;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.sanq.product.config.utils.futures.Future;
import com.sanq.product.config.utils.web.JsonUtil;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import oshi.SystemInfo;
import oshi.hardware.*;
import oshi.software.os.FileSystem;
import oshi.software.os.OSProcess;
import oshi.software.os.OperatingSystem;
import oshi.util.FormatUtil;
import oshi.util.Util;

import javax.annotation.Resource;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.math.BigDecimal;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.CopyOnWriteArraySet;

/**
 * @ServerEndpoint 注解是一个类层次的注解，它的功能主要是将目前的类定义成一个websocket服务器端,
 * 注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端
 */
@ServerEndpoint(value = "/websocket")
public class CommodityServer {

    @Resource
    private ThreadPoolTaskExecutor taskExecutor;
    private Timer t = new Timer();
    private SystemInfo systemInfo = new SystemInfo();

    //concurrent包的线程安全Set，用来存放每个客户端对应的MyWebSocket对象。若要实现服务端与单一客户端通信的话，可以使用Map来存放，其中Key可以为用户标识
    public static CopyOnWriteArraySet<CommodityServer> webSocketSet = new CopyOnWriteArraySet<CommodityServer>();

    //与某个客户端的连接会话，需要通过它来给客户端发送数据
    private Session session;

    /**
     * 连接建立成功调用的方法
     *
     * @param session 可选的参数。session为与某个客户端的连接会话，需要通过它来给客户端发送数据
     */
    @OnOpen
    public void onOpen(Session session) {
        this.session = session;
        webSocketSet.add(this);     //加入set中
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        webSocketSet.remove(this);  //从set中删除
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息
     * @param session 可选的参数
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        HardwareAbstractionLayer hardware = systemInfo.getHardware();
        OperatingSystem operatingSystem = systemInfo.getOperatingSystem();

        ComputerSystem computerSystem = hardware.getComputerSystem();
        CentralProcessor processor = hardware.getProcessor();
        GlobalMemory memory = hardware.getMemory();
        Sensors sensors = hardware.getSensors();


        Future build = new Future.Builder()
                .setExecutor(taskExecutor)
                .option("base", () -> {
                    HashMap<String, Object> osMap = Maps.newLinkedHashMap();
                    {
                        osMap.put("品牌", computerSystem.getManufacturer());
                        osMap.put("型号", computerSystem.getModel());

                        osMap.put("操作系统", String.valueOf(operatingSystem));
                        osMap.put("CPU", processor.getName());
                        osMap.put("总内存", FormatUtil.formatBytes(memory.getTotal()));
                        osMap.put("可用内存", FormatUtil.formatBytes(memory.getAvailable()));
                        osMap.put("开机时间", Instant.ofEpochSecond(operatingSystem.getSystemBootTime()));
                        osMap.put("运行时间", FormatUtil.formatElapsedSecs(operatingSystem.getSystemUptime()));
                    }
                    return osMap;
                })
                .option("process", () -> {
                    LinkedList<Map<String, Object>> list = Lists.newLinkedList();

                    List<OSProcess> procs = Arrays.asList(operatingSystem.getProcesses(20, OperatingSystem.ProcessSort.MEMORY));
                    procs.forEach(process -> {
                        Map<String, Object> processMap = Maps.newHashMap();
                        {
                            processMap.put("PID", process.getProcessID());
                            processMap.put("CPU", CommodityServer.div(process.getKernelTime() + process.getUserTime(), process.getUpTime()) * 100);
                            processMap.put("MEM", CommodityServer.div(process.getResidentSetSize(), memory.getTotal()) * 100);
                            processMap.put("VSZ", FormatUtil.formatBytes(process.getVirtualSize()));
                            processMap.put("RSS", FormatUtil.formatBytes(process.getResidentSetSize()));
                            processMap.put("Name", process.getName());
                        }
                        list.add(processMap);
                    });
                    return list;
                })
                .option("cpu_option", () -> printCpu(operatingSystem, processor, sensors))
                .option("file", () -> printFileSystem(operatingSystem.getFileSystem()))
                .build();


        t.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                Map<String, Object> map = build.run();
                webSocketSet.forEach(item -> {
                    try {
                        item.sendMessage(JsonUtil.obj2Json(map));
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                });
            }
        }, 100L, 1000L);
    }


    /**
     * 发生错误时调用
     *
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        System.out.println("发生错误");
        webSocketSet.clear();
        error.printStackTrace();
    }

    /**
     * 这个方法与上面几个方法不一样。没有用注解，是根据自己需要添加的方法。
     *
     * @param message
     * @throws IOException
     */
    public void sendMessage(String message) throws IOException {
        this.session.getBasicRemote().sendText(message);
    }


    /**
     * 获取盘符信息
     * @param fileSystem
     * @return
     */
    private List<LinkedHashMap<String, Object>> printFileSystem(FileSystem fileSystem) {
        LinkedList<LinkedHashMap<String, Object>> linkedHashMaps = Lists.newLinkedList();

        Arrays.asList(fileSystem.getFileStores()).stream().forEach(item -> {
            long usable = item.getUsableSpace();
            long total = item.getTotalSpace();
            LinkedHashMap<String, Object> map = Maps.newLinkedHashMap();
            {
                map.put("name", item.getName());
                map.put("description", item.getDescription().isEmpty() ? "file system" : item.getDescription());
                map.put("type", item.getType());
                map.put("usable", FormatUtil.formatBytes(usable));
                map.put("total", FormatUtil.formatBytes(item.getTotalSpace()));
                map.put("free", div(100 * usable, total) + "%");
                map.put("mount", item.getMount());
            }
            linkedHashMaps.add(map);
        });

        return linkedHashMaps;
    }

    /**
     * 获取CPU信息
     * @param operatingSystem
     * @param processor
     * @param sensors
     * @return
     */
    private static HashMap<String, Object> printCpu(OperatingSystem operatingSystem, CentralProcessor processor, Sensors sensors) {
        HashMap<String, Object> cpuOption = Maps.newLinkedHashMap();

        long[] prevTicks = processor.getSystemCpuLoadTicks();
        long[][] prevProcTicks = processor.getProcessorCpuLoadTicks();
        Util.sleep(1000);
        long[] ticks = processor.getSystemCpuLoadTicks();
        long user = ticks[CentralProcessor.TickType.USER.getIndex()] - prevTicks[CentralProcessor.TickType.USER.getIndex()];
        long nice = ticks[CentralProcessor.TickType.NICE.getIndex()] - prevTicks[CentralProcessor.TickType.NICE.getIndex()];
        long sys = ticks[CentralProcessor.TickType.SYSTEM.getIndex()] - prevTicks[CentralProcessor.TickType.SYSTEM.getIndex()];
        long idle = ticks[CentralProcessor.TickType.IDLE.getIndex()] - prevTicks[CentralProcessor.TickType.IDLE.getIndex()];
        long iowait = ticks[CentralProcessor.TickType.IOWAIT.getIndex()] - prevTicks[CentralProcessor.TickType.IOWAIT.getIndex()];
        long irq = ticks[CentralProcessor.TickType.IRQ.getIndex()] - prevTicks[CentralProcessor.TickType.IRQ.getIndex()];
        long softirq = ticks[CentralProcessor.TickType.SOFTIRQ.getIndex()] - prevTicks[CentralProcessor.TickType.SOFTIRQ.getIndex()];
        long steal = ticks[CentralProcessor.TickType.STEAL.getIndex()] - prevTicks[CentralProcessor.TickType.STEAL.getIndex()];
        long totalCpu = user + nice + sys + idle + iowait + irq + softirq + steal;

        double[] loadAverage = processor.getSystemLoadAverage(3);
        // per core CPU
        StringBuilder procCpu = new StringBuilder();
        double[] load = processor.getProcessorCpuLoadBetweenTicks(prevProcTicks);
        for (double avg : load) {
            procCpu.append(String.format(" %.1f%%", avg * 100));
        }
        long freq = processor.getMaxFreq();

        {
            cpuOption.put("totalProcessCount", operatingSystem.getProcessCount());
            cpuOption.put("threadCount", operatingSystem.getThreadCount());

            cpuOption.put("User", 100d * user / totalCpu);
            cpuOption.put("Nice", 100d * nice / totalCpu);
            cpuOption.put("System", 100d * sys / totalCpu);
            cpuOption.put("Idle", 100d * idle / totalCpu);
            cpuOption.put("IOwait", 100d * iowait / totalCpu);
            cpuOption.put("IRQ", 100d * irq / totalCpu);
            cpuOption.put("SoftIRQ", 100d * softirq / totalCpu);
            cpuOption.put("Steal", 100d * steal / totalCpu);

            cpuOption.put("CPULOAD", processor.getSystemCpuLoadBetweenTicks(prevTicks) * 100);

            cpuOption.put("procCpu", procCpu.toString());
            cpuOption.put("maxFrequency", FormatUtil.formatHertz(freq));
            cpuOption.put("温度", sensors.getCpuTemperature());
            cpuOption.put("风扇速度", sensors.getFanSpeeds());
            cpuOption.put("CPU倍频", sensors.getCpuVoltage());
        }
        return cpuOption;
    }

    /**
     * 除法
     *
     * @param v1
     * @param v2
     * @return
     */
    public static double div(long v1, long v2) {
        BigDecimal b1 = new BigDecimal(Long.toString(v1));
        BigDecimal b2 = new BigDecimal(Long.toString(v2));

        return b1.divide(b2, 6, BigDecimal.ROUND_HALF_UP).doubleValue();
    }
}
